﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Transactions;
using CH.FourBoards.LagerWalti.Persistence.Entities;
using CH.FourBoards.LagerWalti.Persistence.Setup.Meta;

namespace CH.FourBoards.LagerWalti.Persistence.Setup.Creation
{
    internal abstract class DatabaseInitializer : IDatabaseInitializer<DataContext>
    {
        private readonly IList<IDataGenerator> _generators;

        protected abstract bool RecreateDatabase(DataContext context);

        protected DatabaseInitializer(IList<IDataGenerator> generators)
        {
            _generators = generators;
        }

        public void InitializeDatabase(DataContext context)
        {
            // check if db must be (re-)created
            if (!RecreateDatabase(context)) {
                return;
            }

            // recreate database
            DropDatabase(context);
            context.Database.Create();

            // create data
            using (var newContext = new DataContext()) {
                Seed(newContext);
                newContext.SaveChanges();
            }
        }

        protected void Seed(DataContext context)
        {
            foreach (var generator in _generators) {
                generator.Create(context);
                WriteTestDataMigration(context, generator.GetType());
            }
        }



        protected bool IsCompatible(DataContext context)
        {
            try {
                return context.Database.CompatibleWithModel(true);
            } catch (NotSupportedException) {
                return false;
            }

        }

        #region Database Util

        protected bool DbExists(DataContext context)
        {
            using (new TransactionScope(TransactionScopeOption.Suppress)) {
                return context.Database.Exists();
            }
        }

        private void DropDatabase(DbContext context)
        {
            const string cmd =
               "if (select DB_ID('{0}')) is not null\r\n"
               + "begin\r\n"
               + "alter database [{0}] set offline with rollback immediate;\r\n"
               + "alter database [{0}] set online;\r\n"
               + "drop database [{0}];\r\n"
               + "end";
            context.Database.ExecuteSqlCommand(string.Format(cmd, context.Database.Connection.Database));
        }

        #endregion

        #region SelectStatistics Data Util

        protected bool IsEmpty(DataContext context)
        {
            return !context.TransactionTypes.Any();
        }

        protected bool HasObsoleteData(DataContext context)
        {
            // create unique list of assemblies
            var allAssemblies = _generators.Select(t => new AssemblyInfo(t.GetType()));
            var uniqueAssemblies = new List<AssemblyInfo>();
            foreach (var info in allAssemblies.Where(info => !uniqueAssemblies.Any(i => i.Name.Equals(info.Name)))) {
                uniqueAssemblies.Add(info);
            }

            // check all assemblies
            return uniqueAssemblies.Any(info => IsObsolete(context, info));
        }

        private bool IsObsolete(DataContext context, AssemblyInfo info)
        {
            var log = GetMigrationLog(context, info);
            if (log == null) {
                return true;
            }
            return !log.Version.Equals(info.Version);
        }

        private DataMigration GetMigrationLog(DataContext context, AssemblyInfo info)
        {
            return context.DataMigration.FirstOrDefault(e => e.Assembly == info.Name);
        }

        private void WriteTestDataMigration(DataContext context, Type type)
        {
            var info = new AssemblyInfo(type);
            var log = GetMigrationLog(context, info);
            if (log == null) {
                log = new DataMigration { Assembly = info.Name };
                context.DataMigration.Add(log);
            }
            log.Version = info.Version;
            context.SaveChanges();
        }

        #endregion

    }
}
